package fileStore

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"gitee.com/ling-bin/go-utils/idCounter"
	"gitee.com/ling-bin/repository/appCache"
	"strings"
	"sync"

	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"time"
)

//文件存储客户端字典
var (
	storeClientMap = sync.Map{}
	lock           sync.Mutex // 连接锁定防止并发访问
)

//StoreClient 文件上传客户端
type StoreClient struct {
	scene      string        //场景(可以按这个分类)
	uploadUrl  string        //上传地址
	deleteUrl  string        //删除地址
	timeout    time.Duration //超时时间
	httpClient *http.Client  //http客户端
}

//NewStoreClient 实例化文件上传客户端
//ipport ip和端口
//group  组名称（可以按照这个分集群）
//scene  场景(可以按这个分类)
//timeout 请求超时时间
func NewStoreClient(ipPort string, group string, scene string, timeout time.Duration) IStoreClient {
	//请求组织
	basicUrl := strings.Builder{}
	basicUrl.WriteString(ipPort)
	basicUrl.WriteString("/")
	basicUrl.WriteString(group)
	client, ok := storeClientMap.Load(basicUrl.String())
	if ok {
		return client.(IStoreClient)
	}
	lock.Lock()
	defer lock.Unlock()

	client, ok = storeClientMap.Load(basicUrl.String())
	if ok {
		return client.(IStoreClient)
	}

	basicStr := basicUrl.String()
	newClient := &StoreClient{
		scene:      scene,
		uploadUrl:  fmt.Sprint(basicStr, "/upload"),
		deleteUrl:  fmt.Sprint(basicStr, "/delete?path="),
		timeout:    timeout,
		httpClient: &http.Client{},
	}
	storeClientMap.Store(basicUrl.String(), client)
	return newClient
}

//fileData 文件数据
type fileData struct {
	fileName string          //文件名
	dataMap  map[int][]byte //数据
	maxIndex int            //dataMap key的最大序号
	len      int             //总长度
}

//缓存仓储
var (
	repository  = appCache.GetCacheRepository(&fileData{}) //缓存仓储
	DefaultTime = int64(1800)                              //默认时间
)

//StartDefault 开始文件上传[本地缓存]
//extName 文件扩展名【不带. 如 jpg mp3】
//file    文件数据块
//index  文件段序号（传小于0内部默认按插入顺序从0开始）序号一样会覆盖
//id      返回的索引
//error   数据或扩展名不对时
func (c *StoreClient) StartDefault(extName string, file []byte, index int) (id string, err error) {
	return c.Start(extName, file, index, DefaultTime)
}

//Start 开始文件上传[本地缓存]
//extName 文件扩展名【不带. 如 jpg mp3】
//file    文件数据块
//index  文件段序号（传小于0内部默认按插入顺序从0开始）序号一样会覆盖
//ttl     应用程序缓存时间[秒]
//id      返回的索引
//error   数据或扩展名不对时
func (c *StoreClient) Start(extName string, file []byte, index int, ttl int64) (id string, err error) {
	if len(file) == 0 {
		return "", errors.New("file len not 0")
	}
	if extName == "" {
		return "", errors.New("extName err")
	}
	objectID := idCounter.NewObjectID().Hex()
	fileName := fmt.Sprint(objectID, ".", extName)
	fileData := &fileData{
		fileName: fileName,
		dataMap:  make(map[int][]byte, 10),
		maxIndex: 0,
	}
	if index < 0 {
		index = len(fileData.dataMap)
	}
	fileData.len = len(file)
	fileData.dataMap[index] = file
	if index > fileData.maxIndex {
		fileData.maxIndex = index
	}
	repository.Set(fileName, fileData, ttl)
	return fileName, nil
}

//IsExist 判断当前缓存文件中是否存在指定的包
//id     开始返回的索引
//index  文件段序号（传小于0内部默认按插入顺序从0开始）序号一样会覆盖
//exist  是否存在(true存在，false不存在)
//err     id索引不存在时异常
func (c *StoreClient) IsExist(id string, index int) (bool, error) {
	entity, err := repository.Get(id)
	if err != nil {
		return false, err
	}
	fileData := entity.(*fileData)
	if _, ok := fileData.dataMap[index]; ok {
		return true, nil
	}
	return false, nil
}

// GetIndexSlice 获取缓存了的序号切片
//id     开始返回的索引
//切片    已经缓存的序号切片，按序号排序
//err    id索引不存在时异常
func (c *StoreClient) GetIndexSlice(id string) ([]int,error) {
	entity, err := repository.Get(id)
	if err != nil {
		return nil, err
	}
	fileData := entity.(*fileData)
	ids := make([]int,0,len(fileData.dataMap))
	for i := 0 ; i <= fileData.maxIndex; i++ {
		_, ok := fileData.dataMap[i]
		if !ok {
			continue
		}
		ids = append(ids, i)
	}
	return ids, nil
}

//Continue id    开始返回的索引
//file    文件数据块【不可为空】
//index   文件段序号（传小于0内部默认按插入顺序从0开始）序号一样会覆盖
//count   已缓存包数
//error   本地无缓存或缓存失效时
func (c *StoreClient) Continue(id string, file []byte, index int) (count int64, err error) {
	entity, err := repository.Get(id)
	if err != nil {
		return 0, err
	}
	fileData := entity.(*fileData)
	if len(file) == 0 {
		return int64(len(fileData.dataMap)), errors.New("file len not 0")
	}
	if index < 0 {
		index = len(fileData.dataMap)
	}
	if data, ok := fileData.dataMap[index]; ok {
		fileData.len -= len(data)
	}
	fileData.len += len(file)
	fileData.dataMap[index] = file
	if index > fileData.maxIndex {
		fileData.maxIndex = index
	}
	return int64(len(fileData.dataMap)), nil
}

//Complete id      开始返回的索引
//file    文件数据块【可为空】
//index  文件段序号（传小于0内部默认按插入顺序从0开始）序号一样会覆盖
//error   本地无缓存或缓存失效时
//hf      (完整文件数据，上传的包) (要保存的数据，是否现在保存:false不保存待后续数据 true立刻保存)   【可为空】
func (c *StoreClient) Complete(id string, file []byte, index int, hf func([]byte, []int) ([]byte, bool)) (path string, err error) {
	entity, err := repository.Get(id)
	if err != nil {
		return "", err
	}
	fileData := entity.(*fileData)
	if len(file) != 0 {
		if index < 0 {
			index = len(fileData.dataMap)
		}
		if data, ok := fileData.dataMap[index]; ok {
			fileData.len -= len(data)
		}
		fileData.len += len(file)
		fileData.dataMap[index] = file
		if index > fileData.maxIndex {
			fileData.maxIndex = index
		}
	}
	data := make([]byte, fileData.len)
	ids := make([]int, 0, len(fileData.dataMap))
	var ok bool
	var val []byte
	dataLen := 0
	for i := 0 ; i <= fileData.maxIndex; i++ {
		val, ok = fileData.dataMap[i]
		if !ok {
			continue
		}
		ids = append(ids, i)
		copy(data[dataLen:], val)
		dataLen += len(val)
	}
	if hf != nil {
		data, ok = hf(data, ids)
		if !ok {
			return "", errors.New("等待后续数据！")
		}
	}
	//上传完成移除
	repository.Remove(id)
	return c.upload(fileData.fileName, data)
}

//Upload 文件上传
//extName  文件扩展名【不带. 如 jpg mp3】
//file     完整数据
func (c *StoreClient) Upload(extName string, file []byte) (path string, err error) {
	objectID := idCounter.NewObjectID().Hex()
	if strings.Index(extName, ".") > -1 {
		extNameAry := strings.Split(extName, ".")
		extName = extNameAry[len(extNameAry)-1]
	}
	fileName := fmt.Sprint(objectID, ".", extName)
	return c.upload(fileName, file)
}

//upload 文件上传
//fileName 完整名称
//file     完整数据
func (c *StoreClient) upload(fileName string, file []byte) (path string, err error) {
	body := &bytes.Buffer{}
	// 文件写入 body
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile("file", fileName)
	if err != nil {
		return "", err
	}
	reader := bytes.NewReader(file)
	_, err = io.Copy(part, reader)
	// 其他参数列表写入 body
	writer.WriteField("scene", c.scene)
	writer.WriteField("output", "json2")
	if err := writer.Close(); err != nil {
		return "", err
	}

	httpRequest, err := http.NewRequest("POST", c.uploadUrl, body)
	if err != nil {
		return "", err
	}
	// 添加请求头
	httpRequest.Header.Add("Content-Type", writer.FormDataContentType())
	c.httpClient.Timeout = c.timeout
	// 发送请求
	resp, err := c.httpClient.Do(httpRequest)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	response, _ := ioutil.ReadAll(resp.Body)
	//fmt.Println(string(response))
	result := &ResultData{}
	err = json.Unmarshal(response, result)
	if err != nil {
		return "", err
	}
	if result.Status != "ok" {
		return "", errors.New(string(response))
	}
	return result.Data.Path, nil
}

//Delete 文件删除
func (c *StoreClient) Delete(path string) (ok bool, err error) {
	deleteUrl := strings.Builder{}
	deleteUrl.WriteString(c.deleteUrl)
	deleteUrl.WriteString(path)
	//请求组织
	httpRequest, err := http.NewRequest("Get", deleteUrl.String(), nil)
	if err != nil {
		return false, nil
	}
	// 发送请求
	resp, err := c.httpClient.Do(httpRequest)
	if err != nil {
		return false, nil
	}
	defer resp.Body.Close()
	response, _ := ioutil.ReadAll(resp.Body)
	//fmt.Println(string(response))
	result := &ResultData{}
	err = json.Unmarshal(response, result)
	if err != nil {
		return false, err
	}
	if result.Status != "ok" {
		return false, nil
	}
	return true, nil
}
